/**
 * 一、异步代码改成 Promise
 */
setTimeout(function () {
  var a = 'hello';
  setTimeout(function () {
    var b = 'lagou';
    setTimeout(function () {
      var c = 'I ❤ U';
      console.log(a + b + c);
    }, 10);
  }, 10);
}, 10);

// ! 答案
function f1(val) {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(val);
    }, 10);
  });
}

f1('hello')
  .then(a => {
    return f1(a + 'lagou');
  })
  .then(ab => {
    return f1(ab + 'I ❤ U');
  })
  .then(abc => {
    console.log(abc);
  });

/**
 * 二、基于以下代码完成四个联系
 * */
const fp = require('lodash/fp');

// 数据
// horsepower 马力, dollar_value 价格, in_stock 库存
const cars = [
  {
    name: 'Ferrari FF',
    horsepower: 660,
    dollar_value: 700000,
    in_stock: true,
  },
  {
    name: 'Spyker C12 Zagato',
    horsepower: 650,
    dollar_value: 648000,
    in_stock: false,
  },
  {
    name: 'Jaguar XKR-S',
    horsepower: 648000,
    dollar_value: '',
    in_stock: false,
  },
  {
    name: 'Audi R8',
    horsepower: 525,
    dollar_value: 114200,
    in_stock: false,
  },
  {
    name: 'Astom Martin one-77',
    horsepower: 750,
    dollar_value: 1850000,
    in_stock: true,
  },
  {
    name: 'Pagani Huayra',
    horsepower: 700,
    dollar_value: 1300000,
    in_stock: false,
  },
];

/**
 * 练习1：使用函数组合 fp.flowRight() 重新实现下面函数
 */
let isLastInStock = function (cars) {
  // 获取最后一条数据
  let last_car = fp.last(cars);
  // 获取最后一条数据的 in_stock 属性值
  return fp.prop('in_stock', last_car);
};
console.log('isLastInStock(cars) :', isLastInStock(cars));

// ! 答案
let isInStockOfLast = fp.flowRight(fp.prop('in_stock'), fp.last);
console.log('练习1 :', isInStockOfLast(cars));

/**
 * 练习2：使用 fp.flowRight()、fp.prop()、fp.first()获取第一个car的name
 */
let nameOfFirstCar = fp.flowRight(fp.prop('name'), fp.first);
console.log('练习2 :', nameOfFirstCar(cars));

/**
 * 练习3：使用帮助函数 _average 重构 averageDollarValue ，使用函数组合的方式实现
 */
let _average = function (xs) {
  return fp.reduce(fp.add, 0, xs) / xs.length;
}; // <- 无须改动
let averageDollarValue = function (cars) {
  let dollar_values = fp.map(function (car) {
    return car.dollar_value;
  }, cars);

  return _average(dollar_values);
};
console.log('averageDollarValue :', averageDollarValue(cars));

// ! 答案
let averageValue = fp.flowRight(_average, fp.map('dollar_value'));
console.log('练习3 :', averageValue(cars));

/**
 * 练习4：使用 flowRight 写一个 sanitizeNames() 函数，
 * 返回一个下划线连接的小写字符串，把数组中的 name 转换为这种形式：
 * sanitizeNames(['Hello World']) => ['hello_world']
 */
let _underscore = fp.replace(/\W+/g, '_'); // <- 无需改动，并在 sanitizeNames 中使用它

let sanitizeNames = fp.flowRight(
  fp.map(fp.flowRight(_underscore, fp.lowerCase))
  // fp.map('name')
);
console.log(
  '练习4 :',
  sanitizeNames(['Hello World']),
  sanitizeNames(fp.map('name')(cars))
);

/**
 * 三、基于下面提供的代码，完成后续的四个练习
 */
// support.js
class Container {
  static of(value) {
    return new Container(value);
  }

  constructor(value) {
    this._value = value;
  }

  map(fn) {
    return Container.of(fn(this._value));
  }
}

class MayBe {
  static of(x) {
    return new MayBe(x);
  }

  isNothing() {
    return this._value === undefined || this._value === null;
  }

  constructor(x) {
    this._value = x;
  }

  map(fn) {
    return this.isNothing() ? this : MayBe.of(fn(this._value));
  }
}

/**
 * 练习1：使用 fp.add(x, y) 和 fp.map(f, x) 创建一个
 * 能让 functor 里的值增加的函数 ex1
 */
// app.js
let maybe = MayBe.of([5, 6, 1]);
let ex1 = n => {
  // ! 需要实现的函数
  return maybe.map(x => fp.map(v => fp.add(v, n), x));
};
console.log('三 练习1 ex1(1) :', ex1(1));

/**
 * 练习2：实现一个函数 ex2，能够使用 fp.first 获取列表的第一个元素
 */
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
let ex2 = () => {
  // ! 需要实现的函数
  return xs.map(x => fp.first(x));
};
console.log('三 练习2 :', ex2());

/**
 * 练习3：实现一个函数 ex3，使用 safeProp 和 fp.first 找到 user 的名字的首字母
 */
// app.js
let safeProp = fp.curry(function (x, o) {
  return MayBe.of(o[x]);
});
let user = { id: 2, name: 'Albert' };
let ex3 = () => {
  // ! 需要实现的函数
  return safeProp('name', user).map(x => fp.first(x));
};
console.log('三 练习3 :', ex3());

/**
 * 练习4：使用 MayBe 重写 ex4, 不要有 if 语句
 */

let ex4 = function (n) {
  if (n) {
    return parseInt(n);
  }
};

let ex4New = n => {
  return MayBe.of(n).map(x => parseInt(x));
};
console.log('三 练习4 6.3 :', ex4New(6.3));
console.log('三 练习4 6.6 :', ex4New(6.6));
console.log('三 练习4 null :', ex4New(null));
console.log('三 练习4 undefined :', ex4New());

/**
 * 手写实现 MyPromise 源码
 */

/*
 * [Promises/A+规范](https://www.ituring.com.cn/article/66566)
 * =============
 * @Author: Duyb
 * @Date: 2021-03-16 16:03:26
 * @Last Modified by: Duyb
 * @Last Modified time: 2021-03-17 15:54:06
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

/**
 * Promise 核心逻辑实现
 *
 * 1. Promise 就是一个类，执行的时候，需要传递一个执行器进去，执行器会立即执行
 * 2. Promise 中有三种状态 分别为 成功(fulfilled) 失败(rejected) 等待(pending)
      pending -> fulfilled
      pending -> rejected
      一旦状态确定就不可更改
 * 3. resolve 和 reject 函数是用来更改状态的
 * 4. then 方法内部做的事情就判断状态：若状态是成功，调用成功的回调函数；若状态是失败，调用失败回调函数。then 方法是被定义在原型对象中的
 * 5. then 成功回调传递 value，失败回调传递 reason
 */
class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  // Promise 状态
  status = PENDING;
  // 成功之后的值
  value = undefined;
  // 失败原因
  reason = undefined;

  // ! 实现 then 方法多次调用添加多个处理函数
  // 定义一个成功回调参数，初始化一个空数组
  successCallback = [];
  // 失败回调
  failCallback = [];

  resolve = value => {
    if (this.status !== PENDING) return;

    this.status = FULFILLED;

    // 保存成功之后的值
    this.value = value;
    // console.log('resolve this.value :', this.value);
    // 判断成功回调是否存在，如果存在就调用
    // this.successCallback && this.successCallback(this.value);

    // 判断成功回调是否存在，如果存在就调用
    // 循环回调数组. 把数组前面的方法弹出来并且直接调用
    // shift方法是在数组中删除值，每执行一个就删除一个，最终变为0
    while (this.successCallback.length) this.successCallback.shift()();
  };

  reject = reason => {
    if (this.status !== PENDING) return;

    this.status = REJECTED;

    // 保存失败之后的原因
    this.reason = reason;
    // console.log('reject this.reason :', this.reason);
    // 判断失败回调是否存在，如果存在就调用
    // this.failCallback && this.failCallback(this.reason);

    // 判断失败回调是否存在，如果存在就调用
    // 循环回调数组. 把数组前面的方法弹出来并且直接调用
    while (this.failCallback.length) this.failCallback.shift()();
  };

  then(successCallback, failCallback) {
    // 可选参数
    successCallback = successCallback ? successCallback : value => value;
    failCallback = failCallback
      ? failCallback
      : reason => {
          throw reason;
        };
    // ! 链式调用
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 因为new Promise需要执行完成之后才有promise2，同步代码中没有pormise2，
        // ! 所以这部分代码需要异步执行
        setTimeout(() => {
          // 如果回调中报错的话就执行reject
          try {
            // console.log('then successCallback this.value :', this.value);
            let x = successCallback(this.value);
            // resolve(x);
            // console.log('FULFILLED x :', x);

            // 判断 x 是普通值还是 Promise 对象
            // 是普通值，直接调用 resolve
            // 是 Promise 对象，查看返回的结果
            // 再根据返回结果，决定调用 resolve 还是 reject
            // resolvePromise(x, resolve, reject);

            // 需要判断then之后return的promise对象和原来的是不是一样的，
            // 判断x和promise2是否相等，所以给resolvePromise中传递promise2过去
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            // console.log('then failCallback this.reason :', this.reason);

            let x = failCallback(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else {
        // ! 等待异步
        // 缓存回调方法
        // this.successCallback = successCallback;
        // this.failCallback = failCallback;

        // 等待
        // 将成功回调和失败回调都保存在数组中
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              // console.log('then push successCallback this.value :', this.value);
              let x = successCallback(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              // console.log('then push failCallback this.reason :', this.reason);
              let x = failCallback(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }

  finally(callback) {
    return this.then(
      value => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      reason => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  }

  catch(failCallback) {
    // 直接调用then方法，然后成功的地方传递undefined，错误的地方传递reason
    return this.then(undefined, failCallback);
  }

  static all(arr) {
    let result = [];
    let index = 0; // 计数器
    const len = arr.length;

    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value;
        index += 1;

        // 计数器等于数组长度：所有任务执行完毕
        if (index === len) {
          resolve(result);
        }
      }

      for (let i = 0; i < len; i += 1) {
        let current = arr[i];
        if (current instanceof MyPromise) {
          // promise对象就执行then，如果是resolve就把值添加到数组中去，如果是错误就执行reject返回
          current.then(
            value => addData(i, value),
            reason => reject(reason)
          );
        } else {
          // 普通值就加到对应的数组中去
          addData(i, current);
        }
      }
    });
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;

    return new MyPromise(resolve => resolve(value));
  }

  static race(arr) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < arr.length; i += 1) {
        let current = arr[i];

        if (current instanceof MyPromise) {
          current.then(
            value => resolve(value),
            reason => reject(reason)
          );
        } else {
          resolve(current);
        }
      }
    });
  }

  static allSettled(promises) {
    return this.all(
      promises.map(p =>
        p
          .then(v => ({
            status: FULFILLED,
            value: v,
          }))
          .catch(e => ({
            status: REJECTED,
            reason: e,
          }))
      )
    );
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果相等了，说明return的是自己，抛出类型错误并返回
  if (promise2 === x) {
    return reject(
      new TypeError('Chaining cycle detected for promise #<Promise>')
    );
  }
  if (x instanceof MyPromise) {
    // Promise 对象
    // x.then(
    //   (value) => resolve(value),
    //   (reason) => reject(reason)
    // );

    x.then(resolve, reject);
  } else {
    // 普通值
    resolve(x);
  }
}

module.exports = MyPromise;
